﻿using NPOI.SS.Formula.Functions;

using PLCS.Domain.Shared.Tasks;
using PLCS.PlcInteraction.Models;
using PLCS.Shared.Common.Constant;

using Volo.Abp.DependencyInjection;

namespace PLCS.PlcInteraction;

public class PLCWriter : ITransientDependency
{
    private readonly ILogger<PLCWriter> _logger;
    private static Dictionary<string, S7Client> plcWriteClients;

    public PLCWriter(ILogger<PLCWriter> logger)
    {
        _logger = logger;
        plcWriteClients = new Dictionary<string, S7Client>();
    }

    /// <summary>
    /// 给PLC下发设备任务方法
    /// </summary>
    /// <param name="plc_index">设备在PLC中的序号</param>
    /// <param name="taskId">任务号</param>
    /// <param name="taskType">任务类型</param>
    /// <param name="trayType">托盘类型</param>
    /// <param name="startPos">起始位</param>
    /// <param name="toPos">目标位</param>
    /// <param name="workingStation">工作工位</param>
    /// <param name="trayCount">托盘编号</param>
    /// <returns></returns>
    public bool ExcuteTask(int plc_index, string address, int rack, int slot, int dbNumber, int readDb, int taskIdOffset, PlcWorkParam model)
    {
        // 偏移量等于设备在PLC中的序号*100
        int offset = plc_index * 100;
        var excuteTaskWrite = plcWriteClients.GetOrAdd(address, x => new S7Client());

        var fieldBytes = GetFieldByte_HY(model);

        try
        {
            int res = 0;
            // 先判断client连接
            if (!excuteTaskWrite.Connected)
                res = excuteTaskWrite.ConnectTo(address, rack, slot);

            if (res != 0)
            {
                _logger.LogError($"写入PLC任务异常，异常信息：{excuteTaskWrite.ErrorText(res)}");
                return false;
            }

            res = WritePLCData(offset, fieldBytes, dbNumber, excuteTaskWrite);
            if (taskIdOffset != 0 && readDb != 0)
            {
                return WaitForHandshake(address, readDb, taskIdOffset, model.TaskId);
            }
            if (res != 0)
            {
                _logger.LogError($"写入PLC任务异常，异常信息：{excuteTaskWrite.ErrorText(res)}");
                return false;
            }
            _logger.LogInformation($"写入PLC任务，任务号：{model.TaskId},起始位：{model.StartPos}，目标位 ：{model.ToPos}");

            return true;
        }
        catch (Exception ex)
        {
            plcWriteClients.Remove(address);
            _logger.LogError($"写入PLC任务异常，异常信息：{ex.Message}");
            return false;
        }
    }

    /// <summary>
    /// 写确认收到信号
    /// </summary>
    /// <param name="plc_index"></param>
    /// <param name="address"></param>
    /// <param name="rack"></param>
    /// <param name="slot"></param>
    /// <param name="dbNumber"></param>
    /// <returns></returns>
    public bool ConfirmComplete(int plc_index, string address, int rack, int slot, int dbNumber)
    {
        // 偏移量等于设备在PLC中的序号*100
        int offset = plc_index * 100;
        var excuteTaskWrite = plcWriteClients.GetOrAdd(address, x => new S7Client());
        byte[] sendData = new byte[4];
        S7.SetDIntAt(sendData, 0, 1);
        try
        {
            int res = 0;
            // 先判断client连接
            if (!excuteTaskWrite.Connected)
                res = excuteTaskWrite.ConnectTo(address, rack, slot);

            res = excuteTaskWrite.DBWrite(dbNumber, offset + 92, 4, sendData);
            if (res != 0)
            {
                _logger.LogError($"写入PLC确认信号，异常信息：{excuteTaskWrite.ErrorText(res)}");
                return false;
            }

            if (res != 0)
            {
                _logger.LogError($"写入PLC确认信号异常，异常信息：{excuteTaskWrite.ErrorText(res)}");
                return false;
            }
            _logger.LogInformation($"写入PLC确认信号异常，IP地址：{address}");

            return true;
        }
        catch (Exception ex)
        {
            plcWriteClients.Remove(address);
            _logger.LogError($"写入PLC确认信号异常，异常信息：{ex.Message}");
            return false;
        }
    }

    /// <summary>
    /// 握手
    /// </summary>
    /// <param name="address"></param>
    /// <param name="db"></param>
    /// <param name="start"></param>
    /// <param name="taskId"></param>
    /// <returns></returns>
    private static bool WaitForHandshake(string address, int db, int start, int taskId)
    {
        S7Client hand_Read = plcWriteClients.GetOrAdd(address, x => new S7Client());

        // 设置握手信号默认为未接收到
        bool handshakeReceived = false;

        // 设定最大等待时间为 2 秒
        int maxWaitTimeMs = 2000;
        int elapsedTimeMs = 0;

        int connectResult = 0;
        // 先判断client连接
        if (!hand_Read.Connected)
            connectResult = hand_Read.ConnectTo(address, 0, 1);

        if (connectResult != 0)
        {
            return false;
        }

        // 循环读取握手信号，直到接收到或等待时间超时
        while (!handshakeReceived && elapsedTimeMs < maxWaitTimeMs)
        {
            byte[] buffer = new byte[4];
            var result = hand_Read.DBRead(db, start, 4, buffer);

            // 根据实际情况进行判断和处理
            if (result == 0 && S7.GetDIntAt(buffer, 0) == taskId)
            {
                return true;
            }

            // 累加已等待的时间
            elapsedTimeMs += 100;

            // 可以适当调整读取间隔
            Thread.Sleep(100);
        }
        return false;
    }

    /// <summary>
    /// 获取所有字段的Byte
    /// 如果是货位的话则4*4分别是巷道，排，列，层
    /// 如果是工位的话，则只有第一个Dint赋值，为工位号，别的默认为0
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="taskType"></param>
    /// <param name="trayType"></param>
    /// <param name="startPos"></param>
    /// <param name="toPos"></param>
    /// <param name="workingStation"></param>
    /// <param name="trayCount"></param>
    /// <returns></returns>
    private byte[] GetFieldByte(int taskId, InstructionType taskType, TrayType trayType, string? startPos, string? toPos, string? workingStation, int trayCount = 1)
    {
        byte[] sendData = new byte[100];
        S7.SetDIntAt(sendData, 0, taskId);
        S7.SetDIntAt(sendData, 4, (int)taskType);
        S7.SetDIntAt(sendData, 8, trayCount);
        S7.SetDIntAt(sendData, 12, (int)trayType);
        S7.SetDIntAt(sendData, 48, int.Parse(workingStation ?? "0"));
        S7.SetDIntAt(sendData, 96, 1);
        if (!string.IsNullOrEmpty(startPos))
        {
            var start = SplitPos(startPos);

            S7.SetDIntAt(sendData, 16, int.Parse(start[0]));
            if (start.Length > 1)
            {
                S7.SetDIntAt(sendData, 20, int.Parse(start[1]));
                S7.SetDIntAt(sendData, 24, int.Parse(start[2]));
                S7.SetDIntAt(sendData, 28, int.Parse(start[3]));
            }
        }
        if (!string.IsNullOrEmpty(toPos))
        {
            var to = SplitPos(toPos);
            S7.SetDIntAt(sendData, 32, int.Parse(to[0]));
            if (to.Length > 1)
            {
                S7.SetDIntAt(sendData, 36, int.Parse(to[1]));
                S7.SetDIntAt(sendData, 40, int.Parse(to[2]));
                S7.SetDIntAt(sendData, 44, int.Parse(to[3]));
            }
        }
        return sendData;
    }

    private static byte[] GetFieldByte_HY(PlcWorkParam model)
    {
        byte[] sendData = new byte[100];
        S7.SetDIntAt(sendData, 0, model.TaskId);
        S7.SetDIntAt(sendData, 4, (int)model.TaskType);
        S7.SetDIntAt(sendData, 12, model.TrayCount);
        S7.SetDIntAt(sendData, 8, (int)model.TrayType);
        S7.SetDIntAt(sendData, 24, int.Parse(model.WorkingStation ?? "0"));
        S7.SetDIntAt(sendData, 60, model.MaterialSize);
        S7.SetDIntAt(sendData, 64, model.MovePos);
        S7.SetDIntAt(sendData, 96, 1);
        if (!string.IsNullOrEmpty(model.StartPos))
        {
            var start = model.StartPos.Split(ConstantChar.SplitChar);
            if (start.Length > 1)
            {
                var start1 = start[0].Split('-');
                var start2 = start[1];

                S7.SetDIntAt(sendData, 28, int.Parse(start1[0]));
                S7.SetDIntAt(sendData, 32, int.Parse(start1[1]));
                S7.SetDIntAt(sendData, 36, int.Parse(start1[2]));
                S7.SetDIntAt(sendData, 40, int.Parse(start1[3]));

                S7.SetDIntAt(sendData, 16, int.Parse(start2));
            }
            else
            {
                var start1 = start[0].Split('-');
                if (start1.Length > 1)
                {
                    S7.SetDIntAt(sendData, 28, int.Parse(start1[0]));
                    S7.SetDIntAt(sendData, 32, int.Parse(start1[1]));
                    S7.SetDIntAt(sendData, 36, int.Parse(start1[2]));
                    S7.SetDIntAt(sendData, 40, int.Parse(start1[3]));
                }
                else
                {
                    S7.SetDIntAt(sendData, 16, int.Parse(start1[0]));
                }
            }
        }
        if (!string.IsNullOrEmpty(model.ToPos))
        {
            var to = model.ToPos.Split(ConstantChar.SplitChar);
            if (to.Length > 1)
            {
                var to1 = to[0].Split('-');
                var to2 = to[1];

                S7.SetDIntAt(sendData, 44, Convert.ToInt32(to1[0]));
                S7.SetDIntAt(sendData, 48, Convert.ToInt32(to1[1]));
                S7.SetDIntAt(sendData, 52, Convert.ToInt32(to1[2]));
                S7.SetDIntAt(sendData, 56, Convert.ToInt32(to1[3]));

                S7.SetDIntAt(sendData, 20, int.Parse(to2));
            }
            else
            {
                var to1 = to[0].Split('-');
                if (to1.Length > 1)
                {
                    S7.SetDIntAt(sendData, 44, Convert.ToInt32(to1[0]));
                    S7.SetDIntAt(sendData, 48, Convert.ToInt32(to1[1]));
                    S7.SetDIntAt(sendData, 52, Convert.ToInt32(to1[2]));
                    S7.SetDIntAt(sendData, 56, Convert.ToInt32(to1[3]));
                }
                else
                {
                    S7.SetDIntAt(sendData, 20, int.Parse(to[0]));
                }
            }
        }
        return sendData;
    }

    /// <summary>
    /// 写入PLC
    /// </summary>
    /// <param name="offset"></param>
    /// <param name="fieldBytes"></param>
    /// <param name="dbNumber"></param>
    /// <returns></returns>
    private static int WritePLCData(int offset, byte[] fieldBytes, int dbNumber, S7Client excuteTaskWrite)
    {
        var res = -1;
        if (fieldBytes.Length > 0)
        {
            res = excuteTaskWrite.DBWrite(dbNumber, offset, fieldBytes.Length, fieldBytes);
        }
        return res;
    }

    /// <summary>
    /// 解析工位/货位
    /// </summary>
    /// <param name="pos"></param>
    /// <returns></returns>
    private static string[] SplitPos(string pos)
    {
        return pos.Split('-');
    }
}